import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
import seaborn as sns
plt.style.use('ggplot')
import dalex as dx
from scipy import stats
from scipy.stats import norm
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
import math
import warnings
warnings.filterwarnings('ignore')
np.random.seed(23)
full_df = pd.read_csv('BankChurners.csv')
# We don't need the unique ids'
full_df.drop('CLIENTNUM', axis=1, inplace=True)
full_df.drop(['Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_1'],
inplace=True, axis=1)
full_df.drop(['Naive_Bayes_Classifier_Attrition_Flag_Card_Category_Contacts_Count_12_mon_Dependent_count_Education_Level_Months_Inactive_12_mon_2'],
inplace=True, axis=1)
display(full_df.shape)
(10127, 20)
updated_df = pd.DataFrame()
def tobinary():
# full_df['Attrition_Flag'] = full_df.Attrition_Flag // same thing
updated_df['Attrition'] = full_df.Attrition_Flag.map({'Existing Customer':1, 'Attrited Customer':0})
updated_df['Gender'] = full_df.Gender.map({'M':1, 'F':0})
numeric_columns = ['Customer_Age','Credit_Limit','Months_on_book','Avg_Utilization_Ratio','Total_Trans_Amt','Dependent_count',
'Total_Relationship_Count','Months_Inactive_12_mon','Contacts_Count_12_mon','Total_Revolving_Bal',
'Total_Amt_Chng_Q4_Q1','Total_Ct_Chng_Q4_Q1']
def stringtoint():
missing_income = full_df['Income_Category'].replace({'Unknown': 1 , 'Less than $40K':0, '$40K - $60K':0,
'$80K - $120K':0, '$60K - $80K':0, '$120K +':0})
#missinng data will be replaced with mode:
income_data = full_df['Income_Category'].replace({'Unknown': 1 , 'Less than $40K':1, '$40K - $60K':2,
'$80K - $120K':4, '$60K - $80K':3, '$120K +':5})
missing_education = full_df['Education_Level'].replace({'Unknown': 1, 'High School':0, 'Graduate':0, 'Uneducated':0,
'College':0,'Post-Graduate':0,'Doctorate':0})
#missinng data will be replaced with mode:
education_data = full_df['Education_Level'].replace({'Unknown': 4, 'High School':2, 'Graduate':4, 'Uneducated':1,
'College':3,'Post-Graduate':5,'Doctorate':6})
updated_df['Missing_Income'] = missing_income
updated_df['Income_Category'] = income_data
updated_df['Missing_Education'] = missing_education
updated_df['Education_Level'] = education_data
def encode():
global updated_df
card_dummies = pd.get_dummies(full_df['Card_Category'], prefix='Card')
marital_dummies = pd.get_dummies(full_df['Marital_Status'], prefix='Marital')
updated_df = pd.concat([updated_df, marital_dummies, card_dummies], axis=1)
def concat_with_numerics():
global updated_df
updated_df = pd.concat([updated_df, full_df.loc[:, numeric_columns]], axis=1)
tobinary()
stringtoint()
encode()
concat_with_numerics()
from sklearn.linear_model import LogisticRegression
m_logreg = LogisticRegression()
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(updated_df.drop('Attrition', axis = 1), updated_df.Attrition, test_size=0.2)
m_logreg = m_logreg.fit(X_train, y_train)
from sklearn.metrics import mean_squared_error
y_pred = m_logreg.predict(X_test)
mean_squared_error(y_test, y_pred, squared = False)
0.36166228049558546
from sklearn import metrics
metrics.plot_roc_curve(m_logreg, X_test, y_test)
plt.rcParams['figure.figsize'] = [10, 10]
plt.show()
explainer = dx.Explainer(m_logreg, X_train, y_train)
Preparation of a new explainer is initiated -> data : 8101 rows 25 cols -> target variable : Parameter 'y' was a pandas.Series. Converted to a numpy.ndarray. -> target variable : 8101 values -> model_class : sklearn.linear_model._logistic.LogisticRegression (default) -> label : Not specified, model's class short name will be used. (default) -> predict function : <function yhat_proba_default at 0x000001A6AE989670> will be used (default) -> predict function : Accepts pandas.DataFrame and numpy.ndarray. -> predicted values : min = 0.0527, mean = 0.841, max = 1.0 -> model type : classification will be used (default) -> residual function : difference between y and yhat (default) -> residuals : min = -0.998, mean = -0.00146, max = 0.788 -> model_info : package sklearn A new explainer has been created!
explainer.predict(X_train)[4]
0.8393211454977044
Do zadania został użyty model regresji logicznej. Przglądając różne obserwację otrzymywałam takie wyniki jak: 0,34; 0,76; 0,98. My przyjrzymy się obserwacji o indexie 4, której predykcja wynosi ok.0.84.
pp = explainer.predict_parts(X_train.iloc[4,:], type='shap')
pp.plot()
Dla obserwacji o indexie 4 największą udział w predykcji mają zmienne:
Total_Relationship_Count = 6, klient korzysta intensywnie z usług, więc nie ma zamiaru zamykać kontaTotal_Revolving_Bal = 0, klient ma bardzo ograniczone możliwości korzystania z karty, więc ujemny udział zmiennej jest może być uzasadnionyContacts_Count_12_mon = 4, klient nie zrezygnuje z usługi, ponieważ potrzebuję konta do prowadzenia tranzakcji ze swoimi "kontaktami"pp_shap_C = explainer.predict_parts(X_train.iloc[21,:], type='shap')
pp_shap_C.plot();
pp_shap_D = explainer.predict_parts(X_train.iloc[1,:], type='shap')
pp_shap_D.plot();
Pierwszy:
Total Revolving Bal -0.141Moths Inactive 12 mon 0.061Drugi:
Total Relationship Count-0.12Total Trans Amt 0.069Dla pierwszej obserwacji zmienna Total_Revolving_Bal ma
dużo większy udział niż dla drugiej. Klienci posiadający saldo zerowe
nie mogą korzystać z usługi, co może być dużą motywacją do rezygnacji z
konta. Wsród zmienych mający często duży udział w predykcji, największym
udział dla konkretnej obserwacji mają zmienne, które przyjmują wartości
brzegowe/ odchylone od średniej.
pp_shap = explainer.predict_parts(X_train.iloc[26,:], type='shap')
pp_shap.plot();
pp_shap = explainer.predict_parts(X_train.iloc[30,:], type='shap')
pp_shap.plot();
Tak jak wcześniej zauważyłam, zmienne ktróe przyjmują swoje wartości brzegowe jak np. Total_Relationship_Count,
mają dużą kontrybucję. Dodatkowo dla tej zmiennej można by przypuszczać
że, to czy kontrybucja jest dodatnia lub ujemna zależy od tego którą
wartość brzegową przyjmiemy - 1 lub 5. Podobnie wygląda to dla zmiennej Total_Trans_Amt. Przyjrzyjmy się także zmiennej Months_Inactive_12_mon.
W obu przypadkach przyjmuję ona wartość 3, a jej kontrybucja wynosi
-0.018 w pierwszym i -0.2 w drugi.Są to wartości podobne, ale nie
identyczne.